package bsh;

import jmine.tec.utils.reflection.fast.asm.BytecodeHelper;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.exception.CompilationFailedException;
import bsh.meta.ArrayTypeWrapper;
import bsh.meta.JavaClassWrapper;
import bsh.meta.Scope;
import bsh.meta.TypeWrapper;

/**
 * Compiles an array initialization expression
 * 
 * @author takeshi
 */
public class ArrayInitializerNodeCompiler implements NodeCompiler {

    private static final int NUM_3 = 3;

    private final BSHArrayInitializer node;

    private final CompilationUnit unit;

    /**
     * Constructor
     * 
     * @param node the node to compile
     * @param unit the owner
     */
    public ArrayInitializerNodeCompiler(BSHArrayInitializer node, CompilationUnit unit) {
        super();
        this.node = node;
        this.unit = unit;
    }

    /**
     * {@inheritDoc}
     */
    public void compile(MethodVisitor host, CompilationState state) throws CompilationFailedException {
        if (node.jjtGetNumChildren() == 0) {
            BytecodeHelper.pushIntConst(0, host);
            host.visitTypeInsn(Opcodes.ANEWARRAY, JavaClassWrapper.OBJECT_WRAPPER.internalName());
            state.pushStack(new ArrayTypeWrapper(JavaClassWrapper.OBJECT_WRAPPER));
        } else {
            ArrayTypeWrapper arr = this.nodeResultType(state.currentScope());
            compileArrayInitialization(host, state, arr);
        }
    }

    /**
     * Compiles the array initialization itself
     * 
     * @param host the {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @param arrayType the {@link ArrayTypeWrapper}
     * @throws CompilationFailedException e
     */
    public void compileArrayInitialization(MethodVisitor host, CompilationState state, ArrayTypeWrapper arrayType)
            throws CompilationFailedException {
        TypeWrapper componentType = arrayType.getComponentType();
        int size = node.jjtGetNumChildren();
        BytecodeHelper.pushIntConst(size, host);
        if (componentType instanceof JavaClassWrapper) {
            ((JavaClassWrapper) componentType).allocatePrimitiveArray(host);
        } else {
            host.visitTypeInsn(Opcodes.ANEWARRAY, componentType.internalName());
        }
        state.pushStack(new ArrayTypeWrapper(componentType));
        int opcode = componentType.toASMType().getOpcode(Opcodes.IASTORE);
        for (int i = 0; i < size; i++) {
            host.visitInsn(Opcodes.DUP);
            state.pushStack(state.peekStack());
            // index on the array
            BytecodeHelper.pushIntConst(i, host);
            state.pushStack(int.class);

            int prev = state.getStackSize();
            this.unit.compileNode(node.getChild(i), host, state);
            state.assertStackSize(prev + 1);
            this.unit.getCompilerManager().coerceStackTopTo(host, componentType, state);
            host.visitInsn(opcode);
            state.popStack(NUM_3);
        }
    }

    /**
     * {@inheritDoc}
     */
    public ArrayTypeWrapper nodeResultType(Scope scope) throws CompilationFailedException {
        if (node.jjtGetNumChildren() == 0) {
            return new ArrayTypeWrapper(JavaClassWrapper.OBJECT_WRAPPER);
        }
        TypeWrapper soFar = unit.getNodeType(node.getChild(0), scope);
        for (int i = 1; i < node.jjtGetNumChildren(); i++) {
            soFar = superTypeOf(soFar, unit.getNodeType(node.getChild(i), scope));
        }
        return new ArrayTypeWrapper(soFar);
    }

    /**
     * Tries to find the largest supertype of the two passed
     * 
     * @param soFar {@link TypeWrapper}
     * @param nodeType {@link TypeWrapper}
     * @return {@link TypeWrapper}
     */
    private TypeWrapper superTypeOf(TypeWrapper soFar, TypeWrapper nodeType) {
        if (soFar.equals(nodeType)) {
            return soFar;
        }
        if (soFar.isSuperTypeOf(nodeType)) {
            return soFar;
        } else if (nodeType.isSuperTypeOf(soFar)) {
            return nodeType;
        }
        TypeWrapper largeNumber = JavaClassWrapper.largestNumberType(soFar, nodeType);
        return largeNumber == null ? JavaClassWrapper.OBJECT_WRAPPER : largeNumber;
    }

}
